package com.panopset;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.panopset.compat.Strings;

/**
 * Unique key that is the same between launches.
 * 
 * @author Karl Dinwiddie
 * 
 */
public final class UniqueKey {

	/**
	 * Keys.
	 */
	private static List<String> keys = new ArrayList<String>();

	/**
	 * Map.
	 */
	private static Map<String, Integer> map = new HashMap<String, Integer>();

	/**
	 * Generate a unique key independent of run time, based on stack trace.
	 * 
	 * @return String "key_" + md5sum(getStackTrace)
	 */
	public static String generateStackHashKey() {
		Exception e = null;
		try {
			throw new Exception();
		} catch (Exception ex) {
			e = ex;
		}
		MessageDigest digest;
		try {
			digest = java.security.MessageDigest.getInstance("MD5");
			digest.update(getStackTrace(e).getBytes());
			byte[] hash = digest.digest();
			String rtn = Base64.encode(new String(hash));
			if (keys.contains(rtn)) {
				return generatedIterStackHashKey(rtn);
			}
			keys.add(rtn);
			return rtn;
		} catch (NoSuchAlgorithmException ex) {
			throw new RuntimeException(ex);
		}
	}

	/**
	 * @param key
	 *            Key.
	 * @return Unique iteration for duplicate.
	 */
	private static String generatedIterStackHashKey(final String key) {
		Integer next = map.get(key);
		if (next == null) {
			next = 0;
		}
		String rtn = key + next;
		next++;
		map.put(key, next);
		return rtn;
	}

	/**
	 * @param t
	 *            Throwable to get the stack trace from.
	 * @return Stack trace.
	 */
	public static String getStackTrace(final Throwable t) {
		StringWriter sw = new StringWriter();
		try {
			PrintWriter pw = new PrintWriter(sw);
			t.printStackTrace(pw);
			pw.flush();
			pw.close();
			sw.close();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return sw.toString();
	}

	/**
	 * @param t
	 *            Throwable to get the stack trace from.
	 * @return Full stack trace, including causes.
	 */
	public static String getFullStackTrace(final Throwable t) {
		StringWriter sw = new StringWriter();
		String eol = new Strings().getEol();
		sw.append("See log");
		sw.append(": ");
		sw.append(t.getMessage());
		sw.append(eol);
		sw.append("*************************");
		sw.append(eol);
		sw.append(getStackTrace(t));
		sw.append(eol);
		Throwable cause = t.getCause();
		while (cause != null) {
			sw.append("*************************");
			sw.append(eol);
			sw.append(getStackTrace(cause));
			sw.append(eol);
			cause = cause.getCause();
		}
		return sw.toString();
	}

	/**
	 * Prevent instantiation.
	 */
	private UniqueKey() {
	}
}
